﻿using DotNetCore.CAP;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NewRetail.Application.Components.Platform.Entity;
using NewRetail.Application.Components.Warehouse.Entity;
using NewRetail.Application.Core;
using NewRetail.Application.Utility;
using System;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Json;
using Volo.Abp.Uow;

namespace NewRetail.Application.Worker
{
    public class PosDaySumWorker : MyApplicationService, ITransientDependency, ICapSubscribe
    {
        private readonly ILogger logger;
        private readonly IRepository<PosDaySum> posDaySums;
        private readonly IJsonSerializer jsonSerializer;
        private readonly INewRetailEventBus newRetailEventBus;
        private readonly IUnitOfWorkManager unitOfWorkManager;
        private readonly RedisClient redisClient;
        private readonly RedLockFactoryWrapper redLockFactoryWrapper;

        public PosDaySumWorker(ILogger<PosDaySumWorker> logger,
            IRepository<PosDaySum> posDaySums,
            IJsonSerializer jsonSerializer,
            RedisClient redisClient,
            RedLockFactoryWrapper redLockFactoryWrapper,
            IUnitOfWorkManager unitOfWorkManager,
            INewRetailEventBus newRetailEventBus)
        {
            this.logger = logger;
            this.jsonSerializer = jsonSerializer;
            this.posDaySums = posDaySums;
            this.redisClient = redisClient;
            this.redLockFactoryWrapper = redLockFactoryWrapper;
            this.newRetailEventBus = newRetailEventBus;
            this.unitOfWorkManager = unitOfWorkManager;
        }

        /// <summary>
        /// 计算当天的汇总，如果已经计算就覆盖
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        [CapSubscribe(MessageTopics.PAY_DAY_SUM_CAL)]
        [UnitOfWork]
        public virtual async Task OnMessage(PosDaySumNotifyMessage data)
        {
            var startTime = this.Clock.Now;
            using var uow = this.unitOfWorkManager.Begin(requiresNew: true, isTransactional: true);

            using var redLock = await this.redLockFactoryWrapper.RedLockFactory.CreateLockAsync(
                resource: $"{nameof(PosDaySumWorker)}.{nameof(OnMessage)}.{data.WarehouseId}.{data.ChinaDate.ToShortDateString()}",
                expiryTime: TimeSpan.FromMinutes(30),
                waitTime: TimeSpan.FromMinutes(5),
                retryTime: TimeSpan.FromSeconds(30)
                );
            if (redLock.IsAcquired)
            {
                try
                {
                    var json = this.jsonSerializer.Serialize(data);
                    this.logger.LogInformation($"收到日结计算请求：{json}");

                    var start = data.ChinaDate.Date;
                    var end = start.AddDays(1);

                    var db = await this.posDaySums.GetDbContextAsync();

                    var set = db.Set<PosDaySum>();

                    set.RemoveRange(set.Where(x => x.warehouseID == data.WarehouseId && x.opreateTime >= start && x.opreateTime < end));
                    //先删除当天数据
                    await db.SaveChangesAsync();

                    var query = db.Set<PosSalesFlow>().AsNoTracking().Where(x => x.warehouseID == data.WarehouseId && x.operateTime >= start && x.operateTime < end);

                    var goodIds = await query.Select(x => x.goodsID).Distinct().ToArrayAsync();

                    //this.logger.LogInformation($"商品总数：{goodIds.Count()},所有商品Id:【{string.Join(",", goodIds)}】");
                    this.logger.LogInformation($"商品总数：{goodIds.Count()}");
                    foreach (var gid in goodIds)
                    {
                        var goodQuery = query.Where(x => x.goodsID == gid);
                        
                        var salesQuery = goodQuery.Where(x => x.saleWay == SalesTypes.Sales);
                        var refundQuery = goodQuery.Where(x => x.saleWay == SalesTypes.Refunds);
                        var giveQuery = goodQuery.Where(x => x.saleWay == SalesTypes.Give);

                        var sum = new PosDaySum()
                        {
                            warehouseID = data.WarehouseId,
                            goodsID = gid,
                            opreateTime = start
                        };

                        //销售
                        sum.saleNum = await salesQuery.SumAsync(x => x.saleQuantity);
                        sum.saleAmount = await salesQuery.SumAsync(x => x.saleMoney);

                        //退货
                        sum.returnNum = await refundQuery.SumAsync(x => x.saleQuantity);
                        sum.returnAmount = await refundQuery.SumAsync(x => x.saleMoney);

                        //赠送
                        sum.giveNum = await giveQuery.SumAsync(x => x.saleQuantity);
                        sum.giveAmount = await giveQuery.SumAsync(x => x.saleMoney);

                        //销售单价
                        if (salesQuery.Count() > 0)
                            sum.savePrice = await salesQuery.AverageAsync(x => x.salePrice);
                        else if (refundQuery.Count() > 0)
                            sum.savePrice = await refundQuery.AverageAsync(x => x.salePrice);
                        else if (giveQuery.Count() > 0)
                            sum.savePrice = await giveQuery.AverageAsync(x => x.salePrice);
                        else
                            sum.savePrice = 0;

                        //冲减
                        sum.preNum = -1;
                        sum.preAmount = -1;

                        sum.remark = "...";
                        this.EntityHelper.InitEntity(sum);
                        //this.logger.LogInformation($"日结保存前商品id:{gid},日结数据:{  UtilsHelper.SerializeObject(sum) }");
                        db.Set<PosDaySum>().Add(sum);
                        this.logger.LogInformation($"日结保存成功.商品id:{gid}");

                        //库存变动
                        var warehouseGoods = await db.Set<WarehouseGoods>().FirstOrDefaultAsync(x => x.warehouseID == data.WarehouseId && x.goodsID == gid);
                        if (warehouseGoods != null)
                        {
                            this.logger.LogInformation($"库存变动前商品id:{gid},销售:{sum.saleNum},退货:{sum.returnNum},赠送:{sum.giveNum}");
                            warehouseGoods.goodsNum -= sum.saleNum;
                            warehouseGoods.goodsNum += sum.returnNum;
                            warehouseGoods.goodsNum -= sum.giveNum;
                            this.logger.LogInformation($"库存变动后商品id:{gid},库存:{warehouseGoods.goodsNum}");
                        }

                        await db.SaveChangesAsync();
                    }

                    await uow.CompleteAsync();

                    //通知计算成本价
                    foreach (var m in goodIds)
                    {
                        await this.newRetailEventBus.NotifyWarehouseGoodsCal(new WarehouseGoodsCalMessage()
                        {
                            goodsId = m,
                            warehouseId = data.WarehouseId
                        });
                    }
                }
                catch (Exception e)
                {
                    this.logger.LogError(e, e.Message);

                    await uow.RollbackAsync();
                    throw;
                }
                finally
                {
                    var seconds = (this.Clock.Now - startTime).TotalSeconds;
                    this.logger.LogInformation($"日结计算，消耗{seconds}秒");
                }
            }
            else
            {
                this.logger.LogError(message: "获取锁失败");
                throw new BusinessException("获取锁失败");
            }
        }

        [UnitOfWork]
        public virtual async Task Trigger()
        {
            try
            {
                using var distributedLock = await this.redLockFactoryWrapper.RedLockFactory.CreateLockAsync(
                    resource: $"{nameof(PosDaySumWorker)}",
                    expiryTime: TimeSpan.FromHours(2),
                    waitTime: TimeSpan.FromMinutes(1),
                    retryTime: TimeSpan.FromSeconds(30));

                if (distributedLock.IsAcquired)
                {
                    this.logger.LogInformation("通知日结计算");

                    var chinaNow = this.Clock.Now;

                    var yesterdayStart = chinaNow.AddDays(-1).Date;

                    var db = await this.posDaySums.GetDbContextAsync();
                    var redis = this.redisClient.Connection.GetDatabase(2);

                    var page = 0;

                    while (true)
                    {
                        ++page;
                        var items = await db.Set<Warehouse>().AsNoTracking()
                            .OrderByDescending(x => x.addTime)
                            .Page(page, pageSize: 100).ToArrayAsync();

                        if (!items.Any())
                        {
                            break;
                        }

                        foreach (var m in items)
                        {
                            //判断商品是否计算
                            var key = $"paydaysum.handled.warehouse:{m.Id}&date={yesterdayStart.ToShortDateString()}";
                            if (await redis.KeyExistsAsync(key))
                            {
                                this.logger.LogInformation("已经计算");
                                continue;
                            }

                            var message = new PosDaySumNotifyMessage()
                            {
                                WarehouseId = m.Id,
                                ChinaDate = yesterdayStart
                            };
                            await this.newRetailEventBus.NotifyPayDaySumCal(message);
                            //标记今天计算任务已经下达
                            await redis.StringSetAsync(key: key, value: "计算任务已下达", expiry: TimeSpan.FromDays(2));
                        }
                    }
                }
                else
                {
                    this.logger.LogInformation("未能拿到锁，存在并发");
                }
            }
            catch (Exception e)
            {
                this.logger.LogError(e, e.Message);
                throw;
            }
        }
    }
}
